npm create vite@latest alibabaclone-frontend --template react-ts
App.tsx
and App.css
to 'shared/layout/'index.html
uuid
, react-router-dom
, and axios
npm install uuid react-router-dom axios
npm install @reduxjs/toolkit react-redux
There are different ways to handle CSS
styling when creating components.
You can choose any approach you prefer:
.css
files and import them into your components..module.css
files to automatically scope styles.Create a [dtoName].ts
in the related folder, and define the model
export interface TransportationSearchRequest{
vehicleTypeId ?: number;
fromCityId ?: number;
toCityId ?: number;
startDate ?: Date | null;
endDate ?: Date | null;
}
agent.ts
to handle API calls using axios📂 Suggested Folder: shared/api/
baseURL
to address your web API portimport axios, { AxiosResponse } from 'axios';
import { TransportationSearchRequest } from '../models/transportation/transportationSearchRequest';
import { TransportationSearchResult } from '../models/transportation/transportationSearchResult';
import { City } from '../models/location/city';
axios.defaults.baseURL = 'https://localhost:[REPLACE THIS WITH YOUR BACKEND WEB API PORT]/api';
const responseBody = <T>(response: AxiosResponse<T>) => response.data;
const request = {
get: <T>(url: string) => axios.get<T>(url).then(responseBody),
post: <T>(url: string, body: {}) => axios.post<T>(url, body).then(responseBody),
put: <T>(url: string, body: {}) => axios.put<T>(url, body).then(responseBody),
delete: <T>(url: string) => axios.delete<T>(url).then(responseBody)
}
const TransportationSearch = {
search: (data: TransportationSearchRequest) => request.post<TransportationSearchResult[]>('/transportation/search', data),
}
const Cities = {
list: () => request.get<City[]>('/city'),
}
const agent = {
TransportationSearch,
Cities
}
export default agent;
CityDropdown
Component📂 Suggested Folder: shared/api/
import agent from "@/shared/api/agent";
import { City } from "@/shared/models/location/city";
import { useEffect, useState } from "react";
const CityDropdown = () => {
const [cities, setCities] = useState<City[]>([]);
const [selectedCity, setSelectedCity] = useState<number | undefined>();
useEffect(() => {
agent.Cities.list()
.then(setCities)
.catch((err) => console.error("error loading cities", err));
}, []);
return (
<select
value={selectedCity}
onChange={(e) => setSelectedCity(Number(e.target.value))}
>
<option value="">Select a City</option>
{cities.map((city) => (
<option key={city.id} value={city.id}>
{city.title}
</option>
))}
</select>
);
};
export default CityDropdown;
CityDropdown
component code in the additional infotransportationCard
Componentimport { TransportationSearchResult } from "@/shared/models/transportation/transportationSearchResult";
import React from "react";
interface Props {
transportation: TransportationSearchResult;
}
const TransportationCard: React.FC<Props> = ({ transportation }) => {
return (
<div className="flex items-center justify-between p-4 border rounded-md shadow-md mb-4">
{/* Price and Select Button */}
<div className="flex flex-col items-center">
<div className="text-blue-600 font-bold text-lg">
{transportation.price} Toman
</div>
<button className="bg-blue-500 text-white px-4 py-2 rounded-md mt-2 hover:bg-blue-600">
Select Ticket
</button>
</div>
{/* Trip Info */}
<div className="flex-1 mx-4 text-center">
<div className="font-semibold text-gray-700">
{transportation.companyTitle}
</div>
<div className="flex items-center justify-center mt-2">
<div className="mx-2">{transportation.fromCityTitle}</div>
<span className="text-gray-400">→</span>
<div className="mx-2">{transportation.toCityTitle}</div>
</div>
<div className="text-sm text-gray-500 mt-1">
{new Date(transportation.startDateTime).toLocaleDateString("en", {
hour: "2-digit",
minute: "2-digit",
})}
</div>
</div>
{/* Company Logo or Placeholder */}
<div className="w-12 h-12">
<img
src="/images/company-placeholder.png"
alt="company"
className="w-full h-full object-contain"
/>
</div>
</div>
);
};
export default TransportationCard;
transportationSearchForm
ComponentWhat it is: CORS is a browser security feature that blocks requests to a different domain unless explicitly allowed by the server.
CORS is not about protecting the backend server.
It’s about protecting users from malicious websites using their browser as a weapon.
You’re logged into your bank in one browser tab (bank.com
).
Now, you visit a shady website in another tab (evil.com
). That site has JavaScript that tries to send this:
fetch('https://bank.com/api/transfer?amount=5000&to=hacker', {
credentials: 'include' // it sends your bank cookies!
});
➡️ If the browser allowed this freely, the request would go through using your login session, and you’d lose money.
So the browser says:
“Hold on. This JavaScript is from
evil.com
, and it’s trying to talk tobank.com
. I won’t let that happen unlessbank.com
says it’s okay.”
That’s why the backend server must respond with something like:
Access-Control-Allow-Origin: https://mytrusteddomain.com
Only then will the browser say, “Okay, go ahead.”
✅ To restrict browsers from sending or accepting responses from cross-origin sources
❌ Not to protect the backend
❌ Not to restrict Postman, curl, servers, or mobile apps
http://localhost:5173
https://localhost:7001
agent.ts
const TransportationCard: React.FC<Props> = ({ transportation }) => { ... }
You're using React.FC<Props>
.
✅ FC
stands for Function Component.
React.FC
exactly?React.FC
(or React.FunctionComponent
) is a TypeScript type that you can use to type your functional React components.Props
)Here’s what you get when you use React.FC
:
React.FC
You could just write:
const TransportationCard = ({ transportation }: Props) => { ... }
and it would work!
But you lose some "extra typing safety" like automatic children
typing.
children
When you use React.FC
, TypeScript automatically allows your component to accept children
too — even if you didn’t define it in your Props
.
For example:
<TransportationCard transportation={t}>
<p>Hello</p> // This would be valid automatically
</TransportationCard>
Because children
is always part of a React.FC
.
👉 If you don't use React.FC
, and you want to accept children
, you have to manually add it to your props.
Some people (even in big companies) prefer NOT to use React.FC
anymore because:
So in modern codebases, both styles are OK — it’s just a preference.
Using React.FC |
Not using React.FC |
---|---|
Good for simple, typed functional components | Good if you want full manual control over props |
Auto-includes children prop |
You must manually add children if needed |
Easy and quick | More customizable |
CityDropdown
Component:const [cities, setCities] = useState<City[]>([]);
const [selectedCity, setSelectedCity] = useState<number | undefined>();
cities
: holds the list of cities retrieved from the backend (starts empty []
).selectedCity
: holds the currently selected city’s ID (number
) or undefined
if nothing is selected yet.useEffect(() => {
agent.Cities.list()
.then(setCities)
.catch((err) => console.error("error loading cities", err));
}, []);
[]
dependency array = run once), it calls agent.Cities.list()
. agent.Cities.list()
presumably returns a promise that resolves to an array of City
objects.setCities
updates the cities
state.<select
value={selectedCity}
onChange={(e) => setSelectedCity(Number(e.target.value))}
>
<option value="">Select a City</option>
{cities.map((city) => (
<option key={city.id} value={city.id}>
{city.title}
</option>
))}
</select>
<select>
(dropdown).selectedCity
.onChange
), it updates selectedCity
by converting the selected value
from a string to a number (Number(e.target.value)
).<option>
for each city in the cities
array:
key
and value
are the city's id
.title
. useEffect
?🔹 useEffect
is a React Hook.
It tells React to run some code after the component renders.
Think of it like:
In your code:
useEffect(() => {
agent.Cities.list()
.then(setCities)
.catch((err) => console.error("error loading cities", err));
}, []);
() => { ... }
) runs right after the component is first shown (because of []
— the empty array).agent.Cities.list()
) to get the cities.setCities(data)
.useState
?🔹 useState
is another React Hook.
It creates a piece of memory for your component.
In your code:
const [cities, setCities] = useState<City[]>([]);
const [selectedCity, setSelectedCity] = useState<number | undefined>();
Here’s what is happening:
cities
is a variable that starts as an empty array ([]
).setCities
is a function you use to change the value of cities
.Same with selectedCity
:
selectedCity
starts as undefined
(nothing selected yet).setSelectedCity
lets you update which city is selected.Simple analogy:
Imagine your component is a whiteboard.
useState
gives you a small erasable box on the board.cities
, selectedCity
).setCities
or setSelectedCity
, not your finger (so React knows it changed and redraws the screen if needed).TransportationCard
Componentconst TransportationCard: React.FC<Props> = ({ transportation }) => {
transportation
from props (destructured directly). <div className="flex flex-col items-center">
<div className="text-blue-600 font-bold text-lg">
{transportation.price} Toman
</div>
<button className="bg-blue-500 text-white px-4 py-2 rounded-md mt-2 hover:bg-blue-600">
Select Ticket
</button>
</div>
price
field) styled with blue, bold text.<div className="flex-1 mx-4 text-center">
<div className="font-semibold text-gray-700">
{transportation.companyTitle}
</div>
<div className="flex items-center justify-center mt-2">
<div className="mx-2">{transportation.fromCityTitle}</div>
<span className="text-gray-400">→</span>
<div className="mx-2">{transportation.toCityTitle}</div>
</div>
<div className="text-sm text-gray-500 mt-1">
{new Date(transportation.startDateTime).toLocaleDateString("en", {
hour: "2-digit",
minute: "2-digit",
})}
</div>
</div>
→
) between them.startDateTime
is parsed using new Date(...)
.toLocaleDateString("en", { hour: "2-digit", minute: "2-digit" })
formats it to show just hours and minutes.<div className="w-12 h-12">
<img
src="/images/company-placeholder.png"
alt="company"
className="w-full h-full object-contain"
/>
</div>
object-contain
keeps the image inside the box without stretching.TrasnportationSearchForm
ComponentFeature | Purpose |
---|---|
useState |
To store and manage the form inputs, cities, results, and loading status |
useEffect |
To load the list of cities once when the component appears |
Typing with models | City , TransportationSearchRequest , TransportationSearchResult |
Event handling | To update form values and trigger the search |
Conditional rendering | Show "Loading", "No results", or "Results" dynamically |
const [searchResults, setSearchResults] = useState<TransportationSearchResult[]>([]);
const [loading, setLoading] = useState(false);
const [cities, setCities] = useState<City[]>([]);
const [form, setForm] = useState<TransportationSearchRequest>({
fromCityId: undefined,
toCityId: undefined,
startDate: null,
endDate: null,
vehicleTypeId: undefined,
});
You define 4 states:
searchResults
: List of found transportationsloading
: True/false if waiting for APIcities
: List of available citiesform
: The form input values the user is selectingAll typed properly ✅
useEffect(() => {
agent.Cities.list().then(setCities);
}, []);
Meaning:
agent.Cities.list()
calls your backend, and when it gets the cities, it puts them into cities
state. []
dependency array means this happens only once, not every time anything changes.👉 This is why your cities <select>
dropdown fills up!
const handleChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => { ... }
Whenever a user types/selects:
name
) and what value (value
) they changedform
state accordingly:
fromCityId
and toCityId
are converted to numbers (parseInt
) startDate
and endDate
allow nullconst handleSearch = () => { ... }
When the user clicks Search:
loading
to true
agent.TransportationSearch.search(form)
searchResults
loading
to false
againYou build a UI:
vehicleTypeId
TransportationCard
components for each found item.